#include<iostream>
#include<fstream>
#include<string>
#include <sstream>
#include <vector>
#include <map>

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <pthread.h>

#include "CdnNode.h"
#include "NetworkFw.h"
#include "Logger.h"


void CdnNode::GetLocalTransferPort(int sockfd, string &transfer_port_str)
{
        string local_ip_text;
        networker.GetLocalSockIp(sockfd, local_ip_text);

        stringstream transfer_port_stream;
        transfer_port_stream << local_ip_text << ":" << TNMC_CDN_SERVER_TCP_PORT;
        transfer_port_str = transfer_port_stream.str();
}

void CdnNode::HandleSendMsg(FwAppMsg *msg)
{
    CdnSession *session = static_cast<CdnSession *>(msg->sock_node->data); 
    assert(session->len > 0);
    assert(session->bytes_per_ms);


    fw_file_size_t bytes_due = (get_time_ms() - session->start_time) * session->bytes_per_ms;
    if (bytes_due <= session->bytes_sent) {
        work_queue.enqueue_msg(msg);
        return;
    }

    bytes_due -= session->bytes_sent;
    if (bytes_due > session->len) bytes_due = session->len;
    
    session->dstream->ReadFileBlock(text_buf, bytes_due);
    int bytes_sent = networker.SendFileBlock(session->sockfd, text_buf, bytes_due); 
    if (bytes_sent < 0) {
        throw TNMC_FW_EVENT_CODE_NETWORK_ERR;
    } else if (bytes_sent > 0) {
        session->len -= bytes_sent;
        session->bytes_sent += bytes_sent;
    }

    if (session->len > 0) {
        //send the rest of data later
        work_queue.enqueue_msg(msg);
    } else {
        string http_end = "\r\n0\r\n\r\n";
        int res = networker.SendString(session->sockfd, http_end);
        if (res) {
            throw TNMC_FW_EVENT_CODE_NETWORK_ERR;
        }
    }
}

void CdnNode::ProcessTransferRequest(CdnSession *session)
{
    parser.clear();
    parser.parseTransferRequestMsg(session->request);

    string transfer_id = parser.getParam();
    transfer_id = transfer_id.substr(transfer_id.find("/")+1);
    CdnFileRecord *cf_obj = file_manager.GetFileObjWithId(transfer_id);
    if (!cf_obj) {
        throw TNMC_FW_EVENT_CODE_HTTP404; //cannot find file in database
    }

    fw_file_size_t req_start = parser.getRangeStart();
    fw_file_size_t req_end = parser.getRangeEnd();
    if (req_end == 0) req_end = cf_obj->cdn_file_len;

    if(req_end > cf_obj->cdn_file_len) {
        throw TNMC_FW_EVENT_CODE_HTTP416;//range error
    } else {

        if (!session->dstream) {
            session->dstream = file_manager.OpenDiskStream(cf_obj, req_start);
            if (!session->dstream) { //Unable to open file
                throw TNMC_FW_EVENT_CODE_HTTP404;
            }
        }

        if (session->dstream->GetAbsPos() != req_start) {
            session->dstream->Seek(req_start);
        }

        Respond206(session->sockfd, req_start, req_end, cf_obj->cdn_file_len); 
        session->bytes_sent = 0;
        session->start_time = get_time_ms();
        session->bytes_per_ms = cf_obj->cdn_file_rate/8000.0;
        session->len = req_end - req_start + 1;
        session->state = CDN_SESSION_STATE_BUSY;

        FwAppMsg *msg = new FwAppMsg(FW_APP_MSG_TYPE_SEND, session->sn);
        work_queue.enqueue_msg(msg);
    }

}

void CdnNode::ProcessLocatingRequest(CdnSession *session)
{
    parser.clear();
    parser.parseLocatingRequestMsg(session->request);

    string fileName;
    if (!(parser.getXmlValueByKey("SubType").empty())) {
        fileName = parser.getXmlValueByKey("Name");
    } else {
        fileName = parser.getXmlValueByKey("Object");
    }
    
    CdnFileRecord *cf_obj = file_manager.GetFileObjWithName(fileName);

    if(!cf_obj) { //Unable to find requested file
        throw TNMC_FW_EVENT_CODE_HTTP404;
    } else {
        stringstream range_stream;
        range_stream << "0-" << (cf_obj->cdn_file_len - 1);

        string transfer_port;
        GetLocalTransferPort(session->sockfd, transfer_port);

#if HILI_COM_TEST
        if (!is_index_file) {
            file_manager.GetFileLocation(fileName, transfer_port);
        }
#endif

        string txt = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
            "<LocateResponse>\r\n"
            "<TransferPort>" + transfer_port + "</TransferPort>\r\n" 
            "<TransferID>" + cf_obj->cdn_file_transfer_id + "</TransferID>\r\n"
            "<TransferTimeout>500</TransferTimeout>\r\n"
            "<AvailableRange>" + range_stream.str() + "</AvailableRange>\r\n"
            "<OpenForWrite>no</OpenForWrite>\r\n"
            "</LocateResponse>\r\n";
        
        Respond201(session->sockfd, "text/xml", txt);
    }
}


void CdnNode::HandleRecvMsg(FwAppMsg *msg) {
    CdnSession *session = static_cast<CdnSession *>(msg->sock_node->data); 

    try { 
        networker.ReadBlock(session->sockfd, text_buf, TNMC_HELPER_BUFFER_LEN);
        session->request = text_buf; 
        string status_line = session->request.substr(0, 16);

        if (status_line.find("GET") != string::npos) {
            ProcessTransferRequest(session);
        } else if (status_line.find("POST") != string::npos) {
            ProcessLocatingRequest(session);
        } else {
            logger.log("VSS receives unknown message", LOG_LEVEL_ERROR);
            throw TNMC_FW_EVENT_CODE_HTTP400;
        }

        delete msg;
    } catch (int event_code) {
        switch (event_code) {
            case TNMC_FW_EVENT_CODE_HTTP400:
                Respond400(session->sockfd);
                session->state = CDN_SESSION_STATE_IDLE;
                break;
            case TNMC_FW_EVENT_CODE_HTTP416:
                Respond416(session->sockfd);
                session->state = CDN_SESSION_STATE_IDLE;
                break;
            case TNMC_FW_EVENT_CODE_HTTP404:
                Respond404(session->sockfd);
                session->state = CDN_SESSION_STATE_IDLE;
                break;
            default:
                throw event_code;
        }
        
        delete msg;
    }
}

FileManager file_manager;
Logger logger("log/cdn.log");

int main(int argc, char *argv[])
{
    CdnNode *server = new CdnNode;

    pthread_t precvid;
    int result;
    if ((result = pthread_create(&precvid,NULL, (void* (*)(void*))process_do_networking, server)) == -1) {
        logger.log("VSS thread creation failed", LOG_LEVEL_FATAL);
        return -1;
    }

    process_do_app(server);

    return 0;
}




